#include <sparrow/http_parser.h>
#include <sparrow/http_header_callback.h>

#include <sparrow/variant_headers.h>
#include <sparrow/errors.h>
#include <sparrow/util.h>

static uint8_t *crlf_ = (uint8_t *)"\r\n";
static Octets crlf(crlf_, crlf_ + 2);
static uint8_t *crlf_crlf_ = (uint8_t *)"\r\n\r\n";
static Octets crlf_crlf(crlf_crlf_, crlf_crlf_ + 4);

HttpParser::HttpParser(HttpHeaderCallback *cb)
    :   cb_(cb)
{
}

bool HttpParser::parse_request(const Octets &input, Octets &reminder)
{
    if(!accumulate(input, reminder))
    {
        return false;
    }
    parse_request_line();
    parse_headers();
    return true;
}

bool HttpParser::parse_response(const Octets &input, Octets &reminder)
{
    if(!accumulate(input, reminder))
    {
        return false;
    }
    parse_status_line();
    parse_headers();
    return true;
}

bool HttpParser::accumulate(const Octets &input, Octets &reminder)
{
    // optimize look from last end
    original_buffer_.insert(original_buffer_.end(), input.begin(), input.end());
    _oci end = 
        std::search(original_buffer_.begin(), 
                    original_buffer_.end(), 
                    crlf_crlf.begin(), 
                    crlf_crlf.end());

    if(end == original_buffer_.end())
    {
        return false;
    }
    reminder.assign(end + 4, ((const Octets &)original_buffer_).end());
    original_buffer_.resize(end + 4 - original_buffer_.begin());
    return true;
}

void HttpParser::parse_request_line()
{
    _oci request_line_begin = original_buffer_.begin();
    _oci request_line_end = std::search(original_buffer_.begin(), original_buffer_.end(), crlf.begin(), crlf.end());

    _oci method_begin = request_line_begin;
    _oci method_end = std::find(request_line_begin, request_line_end, ' ');
    if(method_end == request_line_end || method_end == method_begin)
    {
        throw_error<std::runtime_error>(__FILE__, __LINE__,
            "http parser: no method found in [%s]", 
            head(original_buffer_, 40).c_str());
    }

    // allow as in standard - one SP. if needed add eat space func
    _oci url_begin = method_end + 1;
    _oci url_end = std::find(url_begin, request_line_end, ' ');
    if(url_end == request_line_end || url_end == url_begin)
    {
        throw_error<std::runtime_error>(__FILE__, __LINE__,
            "http parser: no uri found in [s]",
            head(original_buffer_, 40).c_str());
    }

    _oci version_begin = url_end + 1;
    _oci version_end = request_line_end;
    if(version_end == version_begin)
    {
        throw_error<std::runtime_error>(__FILE__, __LINE__,
            "http parser: no version found in [%s...]",
            head(original_buffer_, 40).c_str());
    }

 
    Slice method(original_buffer_, method_begin, method_end);
    cb_->method(method);

    Slice version(original_buffer_, version_begin, version_end);
    int major = 0;
    int minor = 0;
    parse_version(version, major, minor);
    cb_->version(version, major, minor);

    Slice uri(original_buffer_, url_begin, url_end);
    Slice schema(original_buffer_, url_begin, url_begin);
    Slice user_info(original_buffer_, url_begin, url_begin);
    Slice host(original_buffer_, url_begin, url_begin);
    int port = 0;
    Slice path(original_buffer_, url_begin, url_begin);
    Slice query(original_buffer_, url_begin, url_begin);
    Slice fragment(original_buffer_, url_begin, url_begin);
    parse_uri(uri, schema, user_info, host, port, path, query, fragment);
    cb_->request_uri(uri, schema, user_info, host, port, path, query, fragment);
    current = request_line_end + 2;
}

void HttpParser::parse_status_line()
{
    _oci response_line_begin = original_buffer_.begin();
    _oci response_line_end = std::search(original_buffer_.begin(), original_buffer_.end(), crlf.begin(), crlf.end());

    _oci version_begin = response_line_begin;
    _oci version_end = std::find(version_begin, response_line_end, (uint8_t)' ');
    if(version_end == response_line_end)
    {
        throw_error<std::runtime_error>(__FILE__, __LINE__,
            "http parser: no version found in [%s...]",
            head(original_buffer_, 40).c_str());
    }

    Slice version(original_buffer_, version_begin, version_end);

    int major = 0;
    int minor = 0;
    parse_version(version, major, minor);
    cb_->version(version, major, minor);

    if(response_line_end - version_end < 6)
    {
        throw_error<std::runtime_error>(__FILE__, __LINE__,
            "http parser: not a valid response line  in [%s...]",
            head(original_buffer_, 40).c_str());
    }

    _oci status_begin = version_end + 1;
    _oci status_end = status_begin + 3;

    
    for(int i = 0; i < 3; ++i)
    {
        if(!isdigit(*(status_begin + i)))
        {
            throw_error<std::runtime_error>(__FILE__, __LINE__,
                "http parser: not a valid response line  in [%s...]",
                head(original_buffer_, 40).c_str());
        }
    }

    std::string status_str(status_begin, status_end);
    VariantInt32 status(status_str);
    
    _oci reason_begin = status_end + 1;
    _oci reason_end = response_line_end;

    Slice reason(original_buffer_, reason_begin, reason_end);
    cb_->status(status.int32(), reason);
    current = response_line_end + 2;
}

void HttpParser::parse_headers()
{
    _oci end = original_buffer_.end();
    _oci header_begin = current;
    _oci header_end = 
        std::search(header_begin, end, crlf.begin(), crlf.end());

    while(header_begin < header_end)
    {
        Slice header(original_buffer_, header_begin, header_end);
        parse_header(header);
        header_begin = header_end + 2;
        header_end = 
            std::search(header_begin, end, crlf.begin(), crlf.end());
    }
}

void HttpParser::parse_header(const Slice &header)
{
    _oci name_begin = header.begin();
    _oci name_end = std::find(name_begin, header.end(), (uint8_t)':');
    if(name_end == header.end())
    {
        throw_error<std::runtime_error>(__FILE__, __LINE__,
            "http parser: not a valid header [%s] in [%s]",
            head(header, 40).c_str(),
            head(original_buffer_, 40).c_str());
    }
    
    _oci value_begin = name_end + 1;
    while(*value_begin++ == (uint8_t)' ');
    --value_begin;
    _oci value_end = header.end();

    while(*value_end-- == (uint8_t)' ');
    ++value_end;

    Slice name(header.buffer(), name_begin, name_end);
    Slice value(header.buffer(), value_begin, value_end);

    cb_->header(header, name, value);
}

void HttpParser::parse_uri(const Slice &uri, 
        Slice &schema,
        Slice &user_info,
        Slice &host,
        int &port,
        Slice &path,
        Slice &query,
        Slice &fragment)
{
   if(uri.begin() == uri.end())
   {
       return;
   }

   _oci current = uri.begin();
   _oci end = uri.end();
   
   if(*current == (uint8_t)'*')
   {
       throw_error<std::runtime_error>(__FILE__, __LINE__,
           "http parser: not implemented [%s...]",
           head(uri.buffer(), 40).c_str());
   }
   else 
   {
       _oci net_path_end = current;
       if(*current != (uint8_t)'/')
       {
           // absoluteURI
           _oci schema_begin = current;
           _oci schema_end = std::find(schema_begin, end, (uint8_t)':');
           if(schema_end == end)
           {
               throw_error<std::runtime_error>(__FILE__, __LINE__,
                   "http parser: not a valid request uri in [%s...]",
                   head(uri.buffer(), 40).c_str());
           }
           schema = Slice(uri.buffer(), schema_begin, schema_end);
           current = schema_end + 1;

           // net_path must follow
           // at least //x
           if(end - current < 3 && *current != (uint8_t)'/' && *(current + 1) != (uint8_t)'/')
           {
               throw_error<std::runtime_error>(__FILE__, __LINE__,
                   "http parser: not a valid request uri in [%s...]",
                   head(uri.buffer(), 40).c_str());
           }
           current += 2;
           _oci net_path_begin = current;
           if(net_path_begin >= end)
           {
               throw_error<std::runtime_error>(__FILE__, __LINE__,
                   "http parser: not a valid request uri in [%s...]",
                   head(uri.buffer(), 40).c_str());
           }
           net_path_end = std::find(net_path_begin, end, (uint8_t)'/');

           _oci user_info_begin = net_path_begin;
           _oci user_info_end = std::find(net_path_begin, net_path_end, (uint8_t)'@');
           if(user_info_end == net_path_end)
           {
               user_info = Slice(uri.buffer(), user_info_begin, user_info_begin);
           }
           else
           {
               user_info = Slice(uri.buffer(), user_info_begin, user_info_end);
               current = user_info_end + 1;
           }
           _oci host_begin = current;
           _oci host_end = std::find(host_begin, net_path_end, (uint8_t)':');
           host = Slice(uri.buffer(), host_begin, host_end);
           if(host_end < net_path_end)
           {
               std::string port_str(host_end + 1, net_path_end);
               VariantInt32 p(port_str);
               port = p.int32();
           }
       }
       // start of abs_path
       const uint8_t *qf = (uint8_t *)"?#";
       _oci abs_path_begin = net_path_end;
       if(*abs_path_begin != (uint8_t)'/')
       {
           throw_error<std::runtime_error>(__FILE__, __LINE__,
               "http parser: not a valid request uri in [%s...]",
               head(uri.buffer(), 40).c_str());
       }

       _oci abs_path_end = std::find_first_of(abs_path_begin, end, qf, qf + 2); 
       path = Slice(uri.buffer(), abs_path_begin, abs_path_end);

       if(abs_path_end == end)
       {
           query = Slice(uri.buffer(), end, end);
           fragment = Slice(uri.buffer(), end, end);
           return;
       }

       if(*abs_path_end == (uint8_t)'?')
       {
           _oci query_begin = abs_path_end + 1;
           _oci query_end = std::find(query_begin, end, (uint8_t)'#');
           query = Slice(uri.buffer(), query_begin, query_end);
           if(*query_end == (uint8_t)'#')
           {
               fragment = Slice(uri.buffer(), query_end + 1, end);
           }
       }
       else // if(*abs_path_end == (uint8_t)'#')
       {
           fragment = Slice(uri.buffer(), abs_path_end + 1, end);
       }
   }
}

void HttpParser::parse_version(const Slice &version, int &major, int &minor)
{
    const char *version_head = "HTTP/";
    _oci major_begin = std::search(version.begin(), version.end(), version_head, version_head + 5);
    if(major_begin != version.begin())
    {
        throw_error<std::runtime_error>(__FILE__, __LINE__,
            "http parser: not a valid version format in [%s...]",
            head(version.buffer(), 40).c_str());
    }

    major_begin += 5;
    _oci major_end = std::find(major_begin, version.end(), '.');
    if(major_end == version.end() || major_end == major_begin)
    {
        throw_error<std::runtime_error>(__FILE__, __LINE__,
            "http parser: not a valid version format in [%s...]",
            head(version.buffer(), 40).c_str());
    }

    _oci minor_begin = major_end + 1;
    _oci minor_end = version.end();
    if(minor_begin == minor_end)
    {
        throw_error<std::runtime_error>(__FILE__, __LINE__,
            "http parser: not a valid version format in [%s...]",
            head(version.buffer(), 40).c_str());
    }
    std::string major_str(major_begin, major_end);
    std::string minor_str(minor_begin, minor_end);

    VariantInt32 major_var(major_str);
    VariantInt32 minor_var(minor_str);
    major = major_var.int32();
    minor = minor_var.int32();
}

